    org 07c00h

;============================================================================
BaseOfStack     equ 07c00h ; Boot状态下堆栈基地址(栈底, 从这个位置向低地址生长)

%include    "load.inc"
;============================================================================


    jmp short LABEL_START
    nop

%include    "fat12hdr.inc"

LABEL_START:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, BaseOfStack

    ; 清屏
    mov ax, 0600h       ; AH = 6,  AL = 0h
    mov bx, 0700h       ; 黑底白字(BL = 07h)
    mov cx, 0           ;  左上角: (0, 0)
    mov dx, 0184fh      ; 右下角: (80, 50)
    int 10h

    mov dh, 0           ; "Booting  "
    call    DispStr     ; 显示字符串

    ; 软驱复位
    xor ah, ah
    xor dl, dl
    int 13h

; 下面在 A 盘的根目录寻找 LOADER.BIN
    mov word [wSectorNo], SectorNoOfRootDirectory

LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
    cmp word [wRootDirSizeForLoop], 0
    jz LABEL_NO_LOADERBIN              ; 判断根目录区是不是已经读完, 如果读完表示没有找到 LOADER.BIN
    dec word [wRootDirSizeForLoop]
    mov ax, BaseOfLoader
    mov es, ax              ; es <- BaseOfLoader
    mov bx, OffsetOfLoader  ; bx <- OffsetOfLoader	于是, es:bx = BaseOfLoader:OffsetOfLoader
    mov ax, [wSectorNo]     ; ax <- Root Directory 中的某 Sector 号
    mov cl, 1
    call    ReadSector

    mov si, LoaderFileName  ; ds:si -> "LOADER  BIN"
    mov di, OffsetOfLoader  ; es:di -> BaseOfLoader:0100 = BaseOfLoader*10h+100
    cld
    mov dx, 10h
LABEL_SEARCH_FOR_LOADERBIN:
    cmp dx, 0               ;循环次数控制, 如果已经读完了一个 Sector, 就跳到下一个 Sector
    jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR
    dec dx
    mov cx, 11
LABEL_CMP_FILENAME:
    cmp cx, 0
    jz  LABEL_FILENAME_FOUND    ; 如果比较了 11 个字符都相等, 表示找到
    dec cx
    lodsb                       ; ds:si -> al
    cmp al, byte [es:di]
    jz LABEL_GO_ON
    jmp LABEL_DIFFERENT         ; 只要发现不一样的字符就表明本 DirectoryEntry 不是

; 我们要找的 LOADER.BIN
LABEL_GO_ON:
    inc di
    jmp LABEL_CMP_FILENAME   

LABEL_DIFFERENT:
    and di, 0ffe0h      ; di &= E0 为了让它指向本条目开头
    add di, 20h         ; di += 20h  下一个目录条目
    mov si, LoaderFileName
    jmp LABEL_SEARCH_FOR_LOADERBIN

LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:
    add word [wSectorNo], 1
    jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN

LABEL_NO_LOADERBIN:
    mov dh, 2       ; "No LOADER."
    call    DispStr
    jmp	$			; 没有找到 LOADER.BIN, 死循环在这里

LABEL_FILENAME_FOUND:   ; 找到 LOADER.BIN 后便来到这里继续
    mov ax, RootDirSectors
    and di, 0ffe0h      ; di -> 当前条目的开始
    add di, 01ah        ; di -> 首 Sector
    mov cx, word [es:di]
    push    cx
    add cx, ax
    add cx, DeltaSectorNo   ; 这句完成时 cl 里面变成 LOADER.BIN 的起始扇区号 (从 0 开始数的序号)
    mov ax, BaseOfLoader
    mov es, ax
    mov bx, OffsetOfLoader
    mov ax, cx

LABEL_GOON_LOADING_FILE:
    push ax
    push bx
    mov ah, 0eh
    mov al, '.'
    mov bl, 0fh
    int 10h
    pop bx
    pop ax

    mov cl, 1
    call    ReadSector
    pop ax              ; 取出此 Sector 在 FAT 中的序号
    call    GetFATEntry
    cmp ax, 0fffh
    jz  LABEL_FILE_LOADED
    push    ax          ; 保存 Sector 在 FAT 中的序号
    mov dx, RootDirSectors
    add ax, dx
    add ax, DeltaSectorNo
    add bx, [BPB_BytsPerSec]
    jmp LABEL_GOON_LOADING_FILE

LABEL_FILE_LOADED:
    mov dh, 1       ; "Ready."
    call    DispStr ; 显示字符串



;============================================================================
;变量
;----------------------------------------------------------------------------
wRootDirSizeForLoop	dw	RootDirSectors	; Root Directory 占用的扇区数, 在循环中会递减至零.
wSectorNo		    dw	0		; 要读取的扇区号
bOdd			    db	0		; 奇数还是偶数

;============================================================================
;字符串
;----------------------------------------------------------------------------
LoaderFileName		db	"LOADER  BIN", 0	; LOADER.BIN 之文件名
; 为简化代码, 下面每个字符串的长度均为 MessageLength
MessageLength		equ	9
BootMessage:		db	"Booting  "; 9字节, 不够则用空格补齐. 序号 0
Message1		    db	"Ready.   "; 9字节, 不够则用空格补齐. 序号 1
Message2		    db	"No LOADER"; 9字节, 不够则用空格补齐. 序号 2
;============================================================================


;----------------------------------------------------------------------------
; 函数名: DispStr
;----------------------------------------------------------------------------
; 作用:
;	显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based)
DispStr:
    mov ax, MessageLength
    mul dh
    add ax, BootMessage
    mov bp, ax
    mov ax, ds
    mov es, ax
    mov cx, MessageLength
    mov ax, 01301h
    mov bx, 0007h
    mov dl, 0
    int 10h
    ret


;----------------------------------------------------------------------------
; 函数名: ReadSector
;----------------------------------------------------------------------------
; 作用:
;	从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中
ReadSector:
	; -----------------------------------------------------------------------
	; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 柱面号, 起始扇区, 磁头号)
	; -----------------------------------------------------------------------
	; 设扇区号为 x
	;                           ┌ 柱面号 = y >> 1
	;       x           ┌ 商 y ┤
	; -------------- => ┤      └ 磁头号 = y & 1
	;  每磁道扇区数     │
	;                   └ 余 z => 起始扇区号 = z + 1
    push    bp
    mov bp, sp
    sub esp, 2      ; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2]

    mov byte [bp-2], cl
    push    bx                  ; 保存 bx
    mov bl, [BPB_SecPerTrk]     ; bl: 除数
    div bl                      ; y 在 al 中, z 在 ah 中
    inc ah                      ; z++
    mov cl, ah                  ; cl <- 起始扇区号
    mov dh, al                  ; dh <- y
    shr al, 1                   ;  y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2)
    mov ch, al                  ; ch <- 柱面号
    and dh, 1                   ; dh & 1 = 磁头号
    pop bx                      ; 恢复 bx

    ; 至此, "柱面号, 起始扇区, 磁头号" 全部得到 ^^^^^^^^^^^^^^^^^^^^^^^^
	mov	dl, [BS_DrvNum]		; 驱动器号 (0 表示 A 盘)
.GoOnReading:
    mov ah, 2                   ; 读
    mov al, byte [bp-2]         ; 读al个扇区
    int 13h
    jc  .GoOnReading            ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止

    add esp, 2
    pop bp

    ret


;----------------------------------------------------------------------------
; 函数名: GetFATEntry
;----------------------------------------------------------------------------
; 作用:
;	找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中
;	需要注意的是, 中间需要读 FAT 的扇区到 es:bx 处, 所以函数一开始保存了 es 和 bx
GetFATEntry:    
    push    es
    push    bx
    push    ax
    mov ax, BaseOfLoader
    sub ax, 0100h           在 BaseOfLoader 后面留出 4K 空间用于存放 FAT
    mov es, ax
    pop ax
    mov byte [bOdd], 0
    mov bx, 3
    mul bx      ; dx:ax = ax * 3
    mov bx, 2
    div bx      ; dx:ax / 2  ==>  ax <- 商, dx <- 余数
    cmp dx, 0
    jz  LABEL_EVEN
    mov byte [bOdd], 1

LABEL_EVEN:
    xor dx, dx          ; 现在 ax 中是 FATEntry 在 FAT 中的偏移量. 下面来计算 FATEntry 在哪个扇区中(FAT占用不止一个扇区)
    mov bx, [BPB_BytsPerSec]
    div bx              ; dx:ax / BPB_BytsPerSec  ==>	ax <- 商   (FATEntry 所在的扇区相对于 FAT 来说的扇区号)

    push    dx
    mov bx, 0           ; bx <- 0	于是, es:bx = (BaseOfLoader - 100):00 = (BaseOfLoader - 100) * 10h
    add ax, SectorNoOfFAT1  ; 此句执行之后的 ax 就是 FATEntry 所在的扇区号
    mov cl, 2
    call    ReadSector  ; 读取 FATEntry 所在的扇区, 一次读两个, 避免在边界发生错误, 因为一个 FATEntry 可能跨越两个扇区
    pop dx
    add bx, dx
    mov ax, [es:bx]
    cmp byte [bOdd], 1
    jnz LABEL_EVEN_2
    shr ax, 4
LABEL_EVEN_2:
    and ax, 0fffh

LABEL_GET_FAT_ENRY_OK:
    pop bx
    pop es
    ret

; ---------------------------------------------------------------------------

times   510-($-$$)  db 0
dw 0xaa55


